﻿using BingSpatialDataServiceExcelAddin.BSDSTools;
using BingSpatialDataServiceExcelAddin.BSDSTools.Models;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using Excel = Microsoft.Office.Interop.Excel;

namespace BingSpatialDataServiceExcelAddin.Views
{
    /// <summary>
    /// Interaction logic for GeocodePanel.xaml
    /// </summary>
    public partial class GeocodePanel : UserControl
    {
        #region Private Methods

        private Settings bsdsSettings;

        #endregion

        #region Constructor

        public GeocodePanel(System.Windows.Forms.Form parent)
        {
            InitializeComponent();

            ParentForm = parent;

            bsdsSettings = new Settings(Globals.ThisAddIn.Application.ActiveWorkbook);

            if (string.IsNullOrWhiteSpace(bsdsSettings.Culture))
            {
                bsdsSettings.Culture = "en-US";
            }

            BingMapsKey.Text = bsdsSettings.MasterKey;
            Culture.Text = bsdsSettings.Culture;
        }

        #endregion

        public System.Windows.Forms.Form ParentForm { get; set; }

        #region Button Handlers

        private void GeocodeBtn_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(BingMapsKey.Text))
            {
                DisplayMessage("Error", "Invalid Bing Maps Key specified.");
                return;
            }

            var sheet = (Excel.Worksheet)Globals.ThisAddIn.Application.ActiveWorkbook.ActiveSheet;

            bool isValid, includesSchemaInfo;
            string notes = "";
            BSDSTools.FileTools.ColumnHeader header;

            DataSourceTools.ValidateDataSource(sheet, out isValid, out includesSchemaInfo, out notes, out header);

            if (!isValid)
            {
                DisplayMessage("Error", "Data Source Invalid.\r\n" + notes);
                ParentForm.Close();
            }
            else
            {
                Excel.Range worksheetCells = DataSourceTools.GetDataRange(sheet, includesSchemaInfo ? "A2" : "A1");
                object[,] data = worksheetCells.Value2 as object[,];
                
                List<GeocodeFeedGeocodeEntity> entities = new List<GeocodeFeedGeocodeEntity>();

                int idIdx = header.GetPrimaryKeyIndex() + 1;
                int latIdx = header.GetIndex("Latitude") + 1;
                int lonIdx = header.GetIndex("Longitude") + 1;

                int addIdx = header.GetIndex("AddressLine") + 1;
                int locIdx = header.GetIndex("Locality") + 1;
                int pcIdx = header.GetIndex("PostalCode") + 1;
                int adIdx = header.GetIndex("AdminDistrict") + 1;
                int crIdx = header.GetIndex("CountryRegion") + 1;

                //TODO: AdminDistrict2, Query, FormattedAddress

                if (addIdx == 0 && locIdx == 0 && pcIdx == 0 && adIdx == 0 && crIdx == 0)
                {
                    DisplayMessage("Error", "Unable to find any valid address columns:\r\nAddressLine, Locality, PostalCode, AdminDistrict, CountryRegion");
                }
                else
                {
                    int numRows = data.GetLength(0);

                    for (int i = 2; i <= numRows; i++)
                    {
                        //Only batch geocode entities that have no values or both lat and lon are 0
                        if (data[i, latIdx] == null || data[i, lonIdx] == null ||
                            (double.Parse(data[i, latIdx].ToString()) == 0 && double.Parse(data[i, lonIdx].ToString()) == 0))
                        {
                            entities.Add(new GeocodeFeedGeocodeEntity()
                            {
                                Id = i.ToString(),
                                GeocodeRequest = new GeocodeFeedGeocodeEntityGeocodeRequest()
                                {
                                    Culture = bsdsSettings.Culture,
                                    Address = new GeocodeFeedGeocodeEntityGeocodeRequestAddress()
                                    {
                                        AddressLine = DataSourceTools.SafeStringValueCast(data, i, addIdx),
                                        Locality = DataSourceTools.SafeStringValueCast(data, i, locIdx),
                                        PostalCode = DataSourceTools.SafeStringValueCast(data, i, pcIdx),
                                        AdminDistrict = DataSourceTools.SafeStringValueCast(data, i, adIdx),
                                        CountryRegion = DataSourceTools.SafeStringValueCast(data, i, crIdx)
                                    }
                                }
                            });
                        }
                    }

                    if (entities.Count == 0)
                    {
                        DisplayMessage("Batch Geocode Log","All Entities are already geocoded.");
                        return;
                    }

                    GeocodeFeed feed = new GeocodeFeed()
                    {
                        GeocodeEntity = entities.ToArray()
                    };

                    BatchGeocoder geocoder = new BatchGeocoder();

                    geocoder.GeocodeCompleted += (x) =>
                    {
                        ElapsedTimer.IsStarted = false;

                        if (!string.IsNullOrWhiteSpace(x.Error))
                        {
                            DisplayMessage("Error", x.Error);
                        }
                        else
                        {
                            StringBuilder sb = new StringBuilder();
                            List<string> ungeocodedIds = new List<string>();

                            sb.AppendFormat("Batch Geocode Job completed in {0}", ElapsedTimer.Text);

                            if (x.Successful != null && x.Successful.GeocodeEntity != null && x.Successful.GeocodeEntity.Length > 0)
                            {
                                sb.AppendFormat("\r\nSuccessfully geocoded {0} entities.\r\n", x.Successful.GeocodeEntity.Length);

                                int row;
                                foreach (GeocodeFeedGeocodeEntity entity in x.Successful.GeocodeEntity)
                                {
                                    row = int.Parse(entity.Id);

                                    if (entity.GeocodeResponse != null && entity.GeocodeResponse.Length > 0)
                                    {
                                        worksheetCells.Cells.set_Item(row, latIdx, entity.GeocodeResponse[0].Point.Latitude);
                                        worksheetCells.Cells.set_Item(row, lonIdx, entity.GeocodeResponse[0].Point.Longitude);
                                    }
                                    else
                                    {
                                        ungeocodedIds.Add(DataSourceTools.SafeStringValueCast(data, row, addIdx));
                                    }
                                }
                            }

                            int totalFailed = ungeocodedIds.Count;

                            if (x.Failed != null && x.Failed.GeocodeEntity != null && x.Failed.GeocodeEntity.Length > 0)
                            {
                                totalFailed += x.Failed.GeocodeEntity.Length;
                            }

                            if(totalFailed > 0)
                            {
                                sb.AppendFormat("\r\nFailed to geocode {0} entities.\r\n", totalFailed);
                                sb.Append("Primary Key: Fault Reason\r\n");

                                int row;
                                string id;

                                if (x.Failed != null && x.Failed.GeocodeEntity != null)
                                {
                                    foreach (GeocodeFeedGeocodeEntity entity in x.Failed.GeocodeEntity)
                                    {
                                        row = int.Parse(entity.Id);
                                        id = DataSourceTools.SafeStringValueCast(data, row, addIdx);

                                        sb.AppendFormat("{0}: {1}\r\n", id, entity.FaultReason);
                                    }
                                }

                                foreach (string i in ungeocodedIds)
                                {
                                    sb.AppendFormat("{0}: Address not found.\r\n", i);
                                }
                            }

                            DisplayMessage("Batch Geocode Log", sb.ToString());
                            ParentForm.Close();
                        }
                    };

                    geocoder.ProgressChanged += (x) =>
                    {
                        Status.Text = x;
                    };

                    geocoder.Geocode(feed, BingMapsKey.Text);

                    ElapsedTimer.IsStarted = true;
                    InputPanel.Visibility = System.Windows.Visibility.Collapsed;
                    StatusPanel.Visibility = System.Windows.Visibility.Visible;

                    //XmlSerializer serializer = new XmlSerializer(typeof(GeocodeFeed));
                    //StringWriter sw = new StringWriter();
                    //serializer.Serialize(sw, feed);
                    //string tx = sw.ToString();
                }
            }
        }

        private void CancelBtn_Click(object sender, RoutedEventArgs e)
        {
            ParentForm.Close();
        }

        #endregion

        #region Private Methods

        private void DisplayMessage(string title, string msg){
            OutputModal form = new OutputModal(title, msg);
            System.Windows.Forms.NativeWindow xlMain = new System.Windows.Forms.NativeWindow();
            xlMain.AssignHandle(new IntPtr(Globals.ThisAddIn.Application.Hwnd));

            form.ShowDialog(xlMain);
            form.Dispose();
            xlMain.ReleaseHandle();
        }

        #endregion
    }
}
